1use super::info::*;
3use crate::ext::io::*;
4use crate::scripts::base::*;
5use crate::types::*;
6use crate::utils::encoding::{decode_to_string, encode_string};
7use anyhow::Result;
8
9#[derive(Debug)]
10pub struct CircusMesScriptBuilder {}
12
13impl CircusMesScriptBuilder {
14 pub const fn new() -> Self {
16 CircusMesScriptBuilder {}
17 }
18}
19
20impl ScriptBuilder for CircusMesScriptBuilder {
21 fn default_encoding(&self) -> Encoding {
22 Encoding::Cp932
23 }
24
25 fn build_script(
26 &self,
27 buf: Vec<u8>,
28 _filename: &str,
29 encoding: Encoding,
30 _archive_encoding: Encoding,
31 config: &ExtraConfig,
32 _archive: Option<&Box<dyn Script>>,
33 ) -> Result<Box<dyn Script>> {
34 Ok(Box::new(CircusMesScript::new(buf, encoding, config)?))
35 }
36
37 fn extensions(&self) -> &'static [&'static str] {
38 &["mes"]
39 }
40
41 fn script_type(&self) -> &'static ScriptType {
42 &ScriptType::Circus
43 }
44
45 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
46 try_parse_header(MemReaderRef::new(&buf[..buf_len])).ok()
47 }
48}
49
50fn try_parse_header(mut data: MemReaderRef<'_>) -> Result<u8> {
51 let head0 = data.read_i32()?;
52 let head1 = data.read_i32()?;
53 if head1 == 0x3 {
54 let offset = head0 as u64 * 0x6 + 0x4;
55 let version = data.peek_u16_at(offset)?;
56 if ScriptInfo::query_by_version(version).is_some() {
57 return Ok(10);
58 }
59 } else {
60 let offset = head0 as u64 * 0x4 + 0x4;
61 let version = data.peek_u16_at(offset)?;
62 if ScriptInfo::query_by_version(version).is_some() {
63 return Ok(10);
64 }
65 }
66 Err(anyhow::anyhow!("Not a Circus MES script"))
67}
68
69#[derive(Debug)]
70struct Token {
71 offset: usize,
72 length: usize,
73 value: u8,
74}
75
76pub struct CircusMesScript {
78 data: Vec<u8>,
79 encoding: Encoding,
80 is_new_ver: bool,
81 version: u16,
82 info: &'static ScriptInfo,
83 asm_bin_offset: usize,
84 blocks_offset: usize,
85 tokens: Vec<Token>,
86}
87
88impl CircusMesScript {
89 pub fn new(data: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
95 let head0 = i32::from_le_bytes(data[0..4].try_into()?);
96 let head1 = i32::from_le_bytes(data[4..8].try_into()?);
97 let mut is_new_ver = false;
98 let mut version = 0;
99 let mut info = config
100 .circus_mes_type
101 .as_ref()
102 .and_then(|name| ScriptInfo::query(name.as_ref()));
103 let mut asm_bin_offset = 0;
104 let mut blocks_offset = 0;
105 if head1 == 0x3 {
106 let offset = head0 * 0x6 + 0x4;
107 if data.len() > offset as usize {
108 if data.len() > offset as usize + 3 {
109 version =
110 u16::from_le_bytes(data[offset as usize..offset as usize + 2].try_into()?);
111 if info.is_none() {
112 info = ScriptInfo::query_by_version(version);
113 }
114 asm_bin_offset = offset as usize + 3;
115 }
116 blocks_offset = 8;
117 }
118 is_new_ver = true;
119 } else {
120 let offset = head0 * 0x4 + 0x4;
121 if data.len() > offset as usize {
122 if data.len() > offset as usize + 2 {
123 version =
124 u16::from_le_bytes(data[offset as usize..offset as usize + 2].try_into()?);
125 if info.is_none() {
126 info = ScriptInfo::query_by_version(version);
127 }
128 asm_bin_offset = offset as usize + 2;
129 }
130 blocks_offset = 4;
131 }
132 }
133 let info = info.ok_or(anyhow::anyhow!("Failed to detect version."))?;
134 let mut tokens = Vec::new();
135 let mut offset = 0;
136 let asm_bin_size = if asm_bin_offset == 0 {
137 0
138 } else {
139 data.len() - asm_bin_offset
140 };
141 while offset < asm_bin_size {
142 let value = data[asm_bin_offset + offset];
143 let length = if info.uint8x2.its(value) {
144 0x03
145 } else if info.uint8str.its(value) {
146 let mut len = 0x3;
147 let mut temp = data[asm_bin_offset + offset + len - 1];
148 while temp != 0x00 {
149 len += 0x1;
150 if asm_bin_offset + offset + len >= data.len() {
151 break;
152 }
153 temp = data[asm_bin_offset + offset + len - 1];
154 }
155 len
156 } else if info.string.its(value) || info.encstr.its(value) {
157 let mut len = 1;
158 let mut temp = data[asm_bin_offset + offset + len - 1];
159 while temp != 0x00 {
160 len += 0x1;
161 if asm_bin_offset + offset + len >= data.len() {
162 break;
163 }
164 temp = data[asm_bin_offset + offset + len - 1];
165 }
166 len
167 } else if info.uint16x4.its(value) {
168 0x09
169 } else {
170 return Err(anyhow::anyhow!(format!(
171 "Unknown token type: 0x{:02X} at offset {}",
172 value,
173 asm_bin_offset + offset
174 )));
175 };
176 let token = Token {
177 offset,
178 length,
179 value,
180 };
181 offset += length;
182 tokens.push(token);
183 }
184 Ok(CircusMesScript {
185 data,
186 encoding,
187 is_new_ver,
188 version,
189 info,
190 asm_bin_offset,
191 blocks_offset,
192 tokens,
193 })
194 }
195}
196
197impl std::fmt::Debug for CircusMesScript {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 f.debug_struct("CircusMesScript")
200 .field("encoding", &self.encoding)
201 .field("is_new_ver", &self.is_new_ver)
202 .field("version", &self.version)
203 .field("info", &self.info)
204 .field("asm_bin_offset", &self.asm_bin_offset)
205 .field("blocks_offset", &self.blocks_offset)
206 .field("tokens", &self.tokens)
207 .finish_non_exhaustive()
208 }
209}
210
211impl Script for CircusMesScript {
212 fn default_output_script_type(&self) -> OutputScriptType {
213 OutputScriptType::Json
214 }
215
216 fn default_format_type(&self) -> FormatOptions {
217 FormatOptions::Fixed {
218 length: 32,
219 keep_original: false,
220 break_words: false,
221 insert_fullwidth_space_at_line_start: true,
222 break_with_sentence: true,
223 #[cfg(feature = "jieba")]
224 break_chinese_words: true,
225 #[cfg(feature = "jieba")]
226 jieba_dict: None,
227 }
228 }
229
230 fn extract_messages(&self) -> Result<Vec<Message>> {
231 let mut mes = vec![];
232 let mut name = None;
233 for token in self.tokens.iter() {
234 let mut t = None;
235 if self.info.encstr.its(token.value) {
236 let mut text = self.data[self.asm_bin_offset + token.offset + 1
237 ..self.asm_bin_offset + token.offset + token.length - 1]
238 .to_vec();
239 for t in text.iter_mut() {
240 *t = (*t).overflowing_add(self.info.deckey).0;
241 }
242 t = Some(decode_to_string(self.encoding, &text, true)?);
243 } else if token.value == self.info.optunenc {
245 let text = &self.data[self.asm_bin_offset + token.offset + 1
246 ..self.asm_bin_offset + token.offset + token.length - 1];
247 t = Some(decode_to_string(self.encoding, text, true)?);
248 }
250 match t {
251 Some(t) => {
252 if token.value == self.info.nameopcode {
253 name = Some(t);
254 } else {
255 let message = Message::new(t, name.take());
256 mes.push(message);
257 }
258 }
259 None => {}
260 }
261 }
262 Ok(mes)
263 }
264
265 fn import_messages<'a>(
266 &'a self,
267 messages: Vec<Message>,
268 writer: Box<dyn WriteSeek + 'a>,
269 _filename: &str,
270 encoding: Encoding,
271 replacement: Option<&'a ReplacementTable>,
272 ) -> Result<()> {
273 let mut repls = Vec::new();
274 if !encoding.is_jis() {
275 fn insert_repl(
276 repls: &mut Vec<(String, String)>,
277 s: &'static str,
278 encoding: Encoding,
279 ) -> Result<()> {
280 let jis = encode_string(Encoding::Cp932, s, true)?;
281 let out = decode_to_string(encoding, &jis, true)?;
282 repls.push((s.to_string(), out));
283 Ok(())
284 }
285 let _ = insert_repl(&mut repls, "{", encoding);
286 let _ = insert_repl(&mut repls, "/", encoding);
287 let _ = insert_repl(&mut repls, "}", encoding);
288 if repls.len() < 3 {
289 eprintln!(
290 "Warning: Some replacements cannot used in current encoding. Ruby text may be broken."
291 );
292 crate::COUNTER.inc_warning();
293 }
294 }
295 if let Some(repl) = replacement {
296 for (k, v) in repl.map.iter() {
297 repls.push((k.to_string(), v.to_string()));
298 }
299 }
300
301 let source = MemReaderRef::new(&self.data);
302 let mut patcher = BinaryPatcher::new(source, writer, |pos| Ok(pos), |pos| Ok(pos))?;
303
304 let mut pending_messages: Vec<Message> = messages.into_iter().rev().collect();
305 let mut current_message = pending_messages.pop();
306 let mut block_updates: Vec<(u64, u32)> = Vec::new();
307 let mut block_index = 0usize;
308
309 for token in &self.tokens {
310 let token_start = (self.asm_bin_offset + token.offset) as u64;
311 patcher.copy_up_to(token_start)?;
312
313 if !self.is_new_ver {
314 let block_offset = (self.blocks_offset + block_index * 4) as u64;
315 let new_offset = patcher.map_offset(token_start)?;
316 let offset_value = (new_offset - self.asm_bin_offset as u64 + 2) as u32;
317 block_updates.push((block_offset, offset_value));
318 block_index += 1;
319 }
320
321 if self.info.is_message_opcode(token.value) {
322 if current_message.is_none() {
323 current_message = pending_messages.pop();
324 if current_message.is_none() {
325 return Err(anyhow::anyhow!("No more messages to import"));
326 }
327 }
328
329 let mut text = {
330 let message = current_message.as_mut().unwrap();
331 if self.info.is_name_opcode(token.value) {
332 match message.name.take() {
333 Some(name) => name,
334 None => {
335 let msg = message.message.clone();
336 current_message = None;
337 msg
338 }
339 }
340 } else {
341 let msg = message.message.clone();
342 current_message = None;
343 msg
344 }
345 };
346
347 for (from, to) in &repls {
348 text = text.replace(from, to);
349 }
350
351 let mut token_bytes = Vec::with_capacity(text.len() + 2);
352 token_bytes.push(token.value);
353 let mut encoded = encode_string(encoding, &text, false)?;
354 if self.info.is_encrypted_message(token.value) {
355 if encoded.contains(&self.info.deckey) {
356 eprintln!(
357 "Warning: text contains deckey 0x{:02X}, text may be truncated: {}",
358 self.info.deckey, text,
359 );
360 crate::COUNTER.inc_warning();
361 }
362 for b in &mut encoded {
363 *b = (*b).overflowing_sub(self.info.deckey).0;
364 }
365 }
366 token_bytes.extend_from_slice(&encoded);
367 token_bytes.push(0x00);
368 patcher.replace_bytes(token.length as u64, &token_bytes)?;
369 continue;
370 }
371
372 if self.is_new_ver && (token.value == 0x03 || token.value == 0x04) {
373 let block_offset = (self.blocks_offset + block_index * 4) as u64;
374 let original_block = patcher.input.cpeek_u32_at(block_offset)?;
375 let new_offset = patcher.map_offset(token_start)?;
376 let offset = (new_offset - self.asm_bin_offset as u64 + token.length as u64) as u32;
377 let value = (original_block & (0xFF << 0x18)) | offset;
378 block_updates.push((block_offset, value));
379 block_index += 1;
380 }
381
382 let token_end = token_start + token.length as u64;
383 patcher.copy_up_to(token_end)?;
384 }
385
386 patcher.copy_up_to(self.data.len() as u64)?;
387
388 for (offset, value) in block_updates {
389 patcher.patch_u32(offset, value)?;
390 }
391
392 Ok(())
393 }
394}